Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.88% covered (success)
95.88%
93 / 97
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Serializer
95.88% covered (success)
95.88%
93 / 97
66.67% covered (warning)
66.67%
4 / 6
28
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
1
 normalize
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
12.05
 denormalizeOnMethodCall
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 denormalizeNewObject
93.33% covered (success)
93.33%
28 / 30
0.00% covered (danger)
0.00%
0 / 1
11.04
 denormalizeOnExistingObject
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Serializer;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Exceptions\InvalidTypeException;
6use Apie\Core\Lists\ItemHashmap;
7use Apie\Core\Lists\ItemList;
8use Apie\Core\Metadata\Concerns\UseContextKey;
9use Apie\Core\Metadata\MetadataFactory;
10use Apie\Core\Utils\ConverterUtils;
11use Apie\Core\ValueObjects\Utils;
12use Apie\Serializer\Context\ApieSerializerContext;
13use Apie\Serializer\Context\NormalizeChildGroup;
14use Apie\Serializer\Exceptions\ValidationException;
15use Apie\Serializer\Lists\NormalizerList;
16use Apie\Serializer\Normalizers\AliasDenormalizer;
17use Apie\Serializer\Normalizers\BooleanNormalizer;
18use Apie\Serializer\Normalizers\DateTimeNormalizer;
19use Apie\Serializer\Normalizers\DateTimeZoneNormalizer;
20use Apie\Serializer\Normalizers\DoNotChangeFileNormalizer;
21use Apie\Serializer\Normalizers\EnumNormalizer;
22use Apie\Serializer\Normalizers\FloatNormalizer;
23use Apie\Serializer\Normalizers\IdentifierNormalizer;
24use Apie\Serializer\Normalizers\IntegerNormalizer;
25use Apie\Serializer\Normalizers\ItemListNormalizer;
26use Apie\Serializer\Normalizers\PaginatedResultNormalizer;
27use Apie\Serializer\Normalizers\PermissionListNormalizer;
28use Apie\Serializer\Normalizers\PolymorphicObjectNormalizer;
29use Apie\Serializer\Normalizers\ReflectionTypeNormalizer;
30use Apie\Serializer\Normalizers\ResourceNormalizer;
31use Apie\Serializer\Normalizers\StringableCompositeValueObjectNormalizer;
32use Apie\Serializer\Normalizers\StringNormalizer;
33use Apie\Serializer\Normalizers\UploadedFileNormalizer;
34use Apie\Serializer\Normalizers\ValueObjectNormalizer;
35use Exception;
36use Psr\Http\Message\UploadedFileInterface;
37use ReflectionClass;
38use ReflectionMethod;
39
40class Serializer
41{
42    use UseContextKey;
43
44    public function __construct(private NormalizerList $normalizers)
45    {
46    }
47
48    public static function create(): self
49    {
50        return new self(new NormalizerList([
51            new AliasDenormalizer(),
52            new PaginatedResultNormalizer(),
53            new DoNotChangeFileNormalizer(),
54            new PermissionListNormalizer(),
55            new UploadedFileNormalizer(),
56            new IdentifierNormalizer(),
57            new StringableCompositeValueObjectNormalizer(),
58            new PolymorphicObjectNormalizer(),
59            new DateTimeNormalizer(),
60            new DateTimeZoneNormalizer(),
61            new ResourceNormalizer(),
62            new EnumNormalizer(),
63            new ValueObjectNormalizer(),
64            new StringNormalizer(),
65            new IntegerNormalizer(),
66            new FloatNormalizer(),
67            new BooleanNormalizer(),
68            new ItemListNormalizer(),
69            new ReflectionTypeNormalizer(),
70        ]));
71    }
72
73    public function normalize(mixed $object, ApieContext $apieContext, bool $forceDefaultNormalization = false): string|int|float|bool|ItemList|ItemHashmap|null
74    {
75        $serializerContext = new ApieSerializerContext($this, $apieContext);
76        if (!$forceDefaultNormalization) {
77            foreach ($this->normalizers->iterateOverNormalizers() as $normalizer) {
78                if ($normalizer->supportsNormalization($object, $serializerContext)) {
79                    return $normalizer->normalize($object, $serializerContext);
80                }
81            }
82        }
83        if (is_array($object)) {
84            $count = 0;
85            $returnValue = [];
86            $isList = true;
87            foreach ($object as $key => $value) {
88                if ($key === $count) {
89                    $count++;
90                } else {
91                    $isList = false;
92                }
93                $returnValue[$key] = $serializerContext->normalizeChildElement($key, $value);
94            }
95            return $isList ? new ItemList($returnValue) : new ItemHashmap($returnValue);
96        }
97        if (!is_object($object)) {
98            if (in_array(get_debug_type($object), ['resource', 'resource (closed)'])) {
99                throw new InvalidTypeException($object, 'primitive');
100            }
101            return $object;
102        }
103        $metadata = MetadataFactory::getResultMetadata(new ReflectionClass($object), $apieContext);
104        $returnValue = [];
105
106        foreach ($metadata->getHashmap()->filterOnContext($apieContext, getters: true) as $fieldName => $metadata) {
107            if ($metadata->isField()) {
108                $returnValue[$fieldName] = $serializerContext->normalizeChildElement(
109                    $fieldName,
110                    $metadata->getValue($object, $apieContext)
111                );
112            }
113        }
114        return new ItemHashmap($returnValue);
115    }
116
117    public function denormalizeOnMethodCall(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $input, ?object $object, ReflectionMethod $method, ApieContext $apieContext): mixed
118    {
119        $serializerContext = new ApieSerializerContext($this, $apieContext);
120        try {
121            $arguments = $serializerContext->denormalizeFromMethod($input, $method);
122        } catch (Exception $error) {
123            throw ValidationException::createFromArray(['' => $error]);
124        }
125        return $method->invokeArgs($object, $arguments);
126    }
127
128    public function denormalizeNewObject(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $object, string $desiredType, ApieContext $apieContext): mixed
129    {
130        if (is_array($object)) {
131            $isList = false;
132            if ($desiredType === 'mixed') {
133                $isList = true;
134                $count = 0;
135                foreach (array_keys($object) as $key) {
136                    if ($key === $count) {
137                        $count++;
138                    } else {
139                        $isList = false;
140                        break;
141                    }
142                }
143            }
144            $object = $isList ? new ItemList($object) : new ItemHashmap($object);
145        }
146        if ($desiredType === 'mixed') {
147            return $object;
148        }
149        $serializerContext = new ApieSerializerContext($this, $apieContext);
150        foreach ($this->normalizers->iterateOverDenormalizers() as $denormalizer) {
151            if ($denormalizer->supportsDenormalization($object, $desiredType, $serializerContext)) {
152                return $denormalizer->denormalize($object, $desiredType, $serializerContext);
153            }
154        }
155        $refl = ConverterUtils::toReflectionClass($desiredType);
156        if (!$refl || !$refl->isInstantiable()) {
157            throw new InvalidTypeException($desiredType, 'a instantiable object');
158        }
159        $metadata = MetadataFactory::getCreationMetadata(
160            $refl,
161            $apieContext
162        );
163        $group = new NormalizeChildGroup(
164            $serializerContext,
165            $metadata
166        );
167        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
168        return $normalizedData->createNewObject();
169    }
170
171    public function denormalizeOnExistingObject(ItemHashmap $object, object $existingObject, ApieContext $apieContext): mixed
172    {
173        $refl = new ReflectionClass($existingObject);
174        $serializerContext = new ApieSerializerContext($this, $apieContext);
175        $metadata = MetadataFactory::getModificationMetadata(
176            $refl,
177            $apieContext
178        );
179        $group = new NormalizeChildGroup(
180            $serializerContext,
181            $metadata
182        );
183        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
184        return $normalizedData->modifyExistingObject($existingObject);
185    }
186}